﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Globalization;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using Sublight.Plugins.SubtitleProvider;
using Sublight.Plugins.SubtitleProvider.Types;

namespace SublightPlugin
{
    /// <summary>
    /// Search provider for OpenSubtitles.org database.
    /// </summary>
    public class OpenSubtitles : BaseSubtitleProvider, ISubtitleProvider
    {
        #region ISubtitleProvider Members
        public Image Logo
        {
            get
            {
                return null;
            }
        }

        public SubtitleAction[] GetActions(string id)
        {
            return new[]
                       {
                           GetVisitHomePage("http://www.opensubtitles.org/")
                       };
        }

        public string GetDownloadUrl(string id)
        {
            return string.Format("http://www.opensubtitles.org/en/subtitles/{0}", id);
        }

        public SubtitleProviderDownloadType DownloadType
        {
            get
            {
                return SubtitleProviderDownloadType.Direct;
            }
        }

        private int _mSeason;
        private int _mEpisode;
        private string[] _mTitleTokens;
        private int _mRecDepth;

        private bool IsTitleMatch(string title)
        {
            if (_mTitleTokens == null) return false;
            if (string.IsNullOrEmpty(title)) return false;

            string[] titleTokens = title.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
            for (int i = 0; i < titleTokens.Length; i++)
            {
                titleTokens[i] = titleTokens[i].Trim().ToLower();
            }

            foreach (string titleToken in _mTitleTokens)
            {
                bool found = false;
                foreach (string currentTitleToken in titleTokens)
                {
                    if (currentTitleToken.Contains(titleToken))
                    {
                        found = true;
                        break;
                    }
                }

                if (!found)
                {
                    return false;
                }
            }

            return true;
        }

        public SubtitleBasicInfo[] Search(string title, SearchFilter[] filter, out string error)
        {
            error = null;
            _mSeason = -1;
            _mEpisode = -1;
            _mTitleTokens = null;
            _mRecDepth = 0;

            try
            {
                if (!string.IsNullOrEmpty(title))
                {
                    _mTitleTokens = title.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
                    for (int i = 0; i < _mTitleTokens.Length; i++)
                    {
                        _mTitleTokens[i] = _mTitleTokens[i].Trim().ToLower();
                    }

                    string season = GetFilterScalar(filter, typeof(SearchFilterSeason));
                    string episode = GetFilterScalar(filter, typeof(SearchFilterEpisode));

                    int iSeason = -1;
                    if (!string.IsNullOrEmpty(season))
                    {
                        if (!Int32.TryParse(season, out iSeason))
                        {
                            iSeason = -1;
                        }
                    }
                    _mSeason = iSeason;

                    var sbSE = new StringBuilder();

                    if (iSeason >= 0)
                    {
                        sbSE.AppendFormat("s{0:00}", iSeason);
                    }

                    int iEpisode = -1;
                    if (!string.IsNullOrEmpty(episode))
                    {
                        if (!Int32.TryParse(episode, out iEpisode))
                        {
                            iEpisode = -1;
                        }
                    }
                    _mEpisode = iEpisode;

                    if (iEpisode >= 0)
                    {
                        sbSE.AppendFormat("e{0:00}", iEpisode);
                    }

                    if (sbSE.Length > 0)
                    {
                        title += string.Format(" {0}", sbSE);
                    }
                }
            }
            catch
            {
            }

            string url = string.Format("http://www.opensubtitles.org/en/search2/sublanguageid-all/moviename-{0}/xml", System.Web.HttpUtility.UrlPathEncode(title));

            //remove season/episode filter because opensubtitles does not provide information about it
            var newFilter = new List<SearchFilter>();
            if (filter != null)
            {
                foreach (SearchFilter sf in filter)
                {
                    if (sf is SearchFilterSeason)
                    {
                        continue;
                    }
                    if (sf is SearchFilterEpisode)
                    {
                        continue;
                    }
                    newFilter.Add(sf);
                }
            }

            try
            {
                var lstResult = new List<SubtitleBasicInfo>();
                SearchSubtitles(url, newFilter.ToArray(), lstResult, true);
                return lstResult.ToArray();
            }
            catch (Exception ex)
            {
                error = string.Format("Error searching subtitles: {0}", ex.Message);
                return null;
            }
            finally
            {
                if (_mWebClient != null)
                {
                    _mWebClient.Dispose();
                    _mWebClient = null;
                }
            }
        }

        private void SearchSubtitles(string url, SearchFilter[] filter, ICollection<SubtitleBasicInfo> lstResult, bool canSearchDeeper)
        {
            if (MaxResultsReached(filter, lstResult.Count))
            {
                return;
            }

            if (_mRecDepth > 10)
            {
                return;
            }

            _mRecDepth++;

            if (!canSearchDeeper)
            {
                url = string.Format("http://www.opensubtitles.org/{0}", url.Trim(new[] { '/' }));
            }
            else
            {
                var wReq = (HttpWebRequest)WebRequest.Create(url);
                wReq.Headers.Add("Accept-Encoding", "identity");
                wReq.KeepAlive = false;
                wReq.UserAgent = UserAgent;
                wReq.Timeout = 15000;

                WebResponse wRes = wReq.GetResponse();
                url = wRes.ResponseUri.AbsoluteUri.Trim(new[] { '/' });
                if (!url.EndsWith("/xml", StringComparison.InvariantCultureIgnoreCase))
                {
                    url += "/xml";
                }
                wRes.Close();
            }

            string data = WebClient.DownloadString(url);

            var xml = new XmlDocument();
            try
            {
                xml.LoadXml(data);
            }
            catch (Exception)
            {
                return;
            }

            XmlNodeList xnl = xml.SelectNodes("/opensubtitles/search/results/subtitle");
            if (xnl != null)
            {
                foreach (XmlNode xn in xnl)
                {
                    XmlNode idSubtitle = xn.SelectSingleNode("IDSubtitle");
                    if (idSubtitle == null) continue;

                    XmlAttribute xa = idSubtitle.Attributes["DownloadLink"];
                    if (xa != null)
                    {
                        SubtitleInfo si = CreateSubtitleInfo(xn);
                        if (si != null)
                        {
                            try
                            {
                                if (DoesMatchFilter(si, filter)) //this is optional but brings better performance
                                {
                                    if (!IsTitleMatch(si.Title))
                                    {
                                        continue;
                                    }

                                    bool matchSE;

                                    if (_mSeason >= 0 && _mEpisode >= 0)
                                    {
                                        matchSE = false;

                                        if (!string.IsNullOrEmpty(si.Release))
                                        {
                                            if (Regex.IsMatch(si.Release, string.Format("S0*?{0}E0*?{1}", _mSeason, _mEpisode), RegexOptions.IgnoreCase))
                                            {
                                                si.Season = Convert.ToInt16(_mSeason);
                                                si.Episode = Convert.ToInt16(_mEpisode);
                                                matchSE = true;
                                            }
                                            else if (Regex.IsMatch(si.Release, string.Format("0*?{0}x0*?{1}", _mSeason, _mEpisode), RegexOptions.IgnoreCase))
                                            {
                                                si.Season = Convert.ToInt16(_mSeason);
                                                si.Episode = Convert.ToInt16(_mEpisode);
                                                matchSE = true;
                                            }
                                        }
                                    }
                                    else
                                    {
                                        matchSE = true;
                                    }

                                    if (matchSE)
                                    {
                                        lstResult.Add(si);
                                    }

                                    if (MaxResultsReached(filter, lstResult.Count))
                                    {
                                        return;
                                    }
                                }
                            }
                            catch
                            {
                                continue;
                            }
                        }
                    }
                    else
                    {
                        if (canSearchDeeper)
                        {
                            string newUrl = xn.SelectSingleNode("MovieID").Attributes["Link"].InnerText;

                            if (!string.IsNullOrEmpty(newUrl))
                            {
                                newUrl = newUrl.Trim(new[] { '/' });
                                newUrl = string.Format("http://www.opensubtitles.org/{0}", newUrl);
                                SearchSubtitles(newUrl, filter, lstResult, true);
                            }
                        }
                    }
                }
            }
        }

        private Language LanguageIdToLanguage(string id)
        {
            try
            {
                return LanguageMapper.GetLanguage(id);
            }
            catch
            {
                return Language.Unknown;
            }
        }

        private SubtitleInfo CreateSubtitleInfo(XmlNode subtitle)
        {
            var si = new SubtitleInfo
                         {
                             Id = GetValue(subtitle, "IDSubtitle").Trim(),
                             Language = LanguageIdToLanguage(GetValue(subtitle, "LanguageName")),
                             Title = GetValue(subtitle, "MovieName").Trim()
                         };

            if (!si.AreMandatoryFieldsSet)
            {
                return null;
            }

            si.Release = GetValue(subtitle, "MovieReleaseName");
            si.PublishDate = DateTime.ParseExact(GetValue(subtitle, "SubDate"), "yyyy-MM-dd", CultureInfo.InvariantCulture);
            si.MediaCount = byte.Parse(GetValue(subtitle, "SubSumCD"));
            si.MediaFormat = MediaFormat.CD;
            si.Year = Int32.Parse(GetValue(subtitle, "MovieYear"));
            si.Publisher = GetValue(subtitle, "UserNickName");

            double fps;
            if (double.TryParse(GetValue(subtitle, "MovieFPS"), NumberStyles.Float, CultureInfoEN, out fps))
            {
                if (fps == 25)
                {
                    si.FPS = FPS.FPS_25;
                }
                else if (fps == 23.976)
                {
                    si.FPS = FPS.FPS_23_976;
                }
                else if (fps == 29.970)
                {
                    si.FPS = FPS.FPS_29_97;
                }
                else
                {
                    si.FPS = FPS.NotAvailable;
                }
            }
            else
            {
                si.FPS = FPS.NotAvailable;
            }

            si.Imdb = string.Format("http://www.imdb.com/title/tt{0}", GetValue(subtitle, "MovieImdbID"));
            return si;
        }

        private static string GetValue(XmlNode subtitle, string xpath)
        {
            XmlNode xn = subtitle.SelectSingleNode(xpath);
            if (xn == null) return null;
            return xn.InnerText;
        }

        public byte[] DownloadById(string id)
        {
            try
            {
                return WebClient.DownloadData(String.Format("http://www.opensubtitles.org/en/download/sub/{0}", id));
            }
            catch
            {
                return null;
            }
        }

        public string ShortName
        {
            get
            {
                return "OpenSubtitles.org";
            }
        }

        public string Info
        {
            get
            {
                return "OpenSubtitles.org subtitle provider";
            }
        }

        public Version Version
        {
            get
            {
                return new Version(1, 0, 0);
            }
        }
        #endregion

        #region WebClient
        protected WebClient WebClient
        {
            get
            {
                if (_mWebClient == null)
                {
                    _mWebClient = new WebClient();

                    var client = new WebClient();
                    client.Headers.Add("Accept-Encoding", "identity");
                    client.Headers.Add("Connection", "close");
                    client.Headers.Add("User-Agent", UserAgent);
                }

                return _mWebClient;
            }
        }

        private WebClient _mWebClient;
        #endregion

        #region LanguageMapper
        protected override LanguageMapperUtility LanguageMapper
        {
            get
            {
                if (_mLanguageMapper == null)
                {
                    _mLanguageMapper = new LanguageMapperUtility();
                    _mLanguageMapper.Map(Language.Slovenian, "Slovenian");
                    _mLanguageMapper.Map(Language.English, "English");
                    _mLanguageMapper.Map(Language.German, "German");
                    _mLanguageMapper.Map(Language.Czech, "Czech");
                    _mLanguageMapper.Map(Language.French, "French");
                    _mLanguageMapper.Map(Language.Italian, "Italian");
                    _mLanguageMapper.Map(Language.Arabic, "Arabic");
                    _mLanguageMapper.Map(Language.Romanian, "Romanian");
                    _mLanguageMapper.Map(Language.Hungarian, "Hungarian");
                    _mLanguageMapper.Map(Language.Greek, "Greek");
                    _mLanguageMapper.Map(Language.Chinese, "Chinese");
                    _mLanguageMapper.Map(Language.Estonian, "Estonian");
                    _mLanguageMapper.Map(Language.Hebrew, "Hebrew");
                    _mLanguageMapper.Map(Language.Dutch, "Dutch");
                    _mLanguageMapper.Map(Language.Danish, "Danish");
                    _mLanguageMapper.Map(Language.Swedish, "Swedish");
                    _mLanguageMapper.Map(Language.Polish, "Polish");
                    _mLanguageMapper.Map(Language.Russian, "Russian");
                    _mLanguageMapper.Map(Language.Spanish, "Spanish");
                    _mLanguageMapper.Map(Language.Turkish, "Turkish");
                    _mLanguageMapper.Map(Language.Finnish, "Finnish");
                    _mLanguageMapper.Map(Language.Portuguese, "Portuguese");
                    _mLanguageMapper.Map(Language.SerbianLatin, "Serbian");
                    _mLanguageMapper.Map(Language.Slovak, "Slovak");
                    _mLanguageMapper.Map(Language.Croatian, "Croatian");
                    _mLanguageMapper.Map(Language.PortugueseBrazil, "Portuguese");
                    _mLanguageMapper.Map(Language.Indonesian, "Indonesian");
                    _mLanguageMapper.Map(Language.Persian, "Persian");
                    _mLanguageMapper.Map(Language.Bulgarian, "Bulgarian");
                    _mLanguageMapper.Map(Language.Japanese, "Japanese");
                    _mLanguageMapper.Map(Language.Albanian, "Albanian");
                    _mLanguageMapper.Map(Language.Belarusian, "Belarusian");
                    _mLanguageMapper.Map(Language.BosnianLatin, "Bosnian");
                    _mLanguageMapper.Map(Language.Hindi, "Hindi");
                    _mLanguageMapper.Map(Language.Irish, "Irish");
                    _mLanguageMapper.Map(Language.Icelandic, "Icelandic");
                    _mLanguageMapper.Map(Language.Catalan, "Catalan");
                    _mLanguageMapper.Map(Language.Korean, "Korean");
                    _mLanguageMapper.Map(Language.Latvian, "Latvian");
                    _mLanguageMapper.Map(Language.Lithuanian, "Lithuanian");
                    _mLanguageMapper.Map(Language.Macedonian, "Macedonian");
                    _mLanguageMapper.Map(Language.Norwegian, "Norwegian");
                    _mLanguageMapper.Map(Language.Thai, "Thai");
                    _mLanguageMapper.Map(Language.Ukrainian, "Ukrainian");
                    _mLanguageMapper.Map(Language.Vietnamese, "Vietnamese");
                }
                return _mLanguageMapper;
            }
        }
        private LanguageMapperUtility _mLanguageMapper;
        #endregion

        protected CultureInfo CultureInfoEN
        {
            get
            {
                if (_mCultureInfoEn == null)
                {
                    _mCultureInfoEn = new CultureInfo("en-US");
                }
                return _mCultureInfoEn;
            }
        }

        private CultureInfo _mCultureInfoEn;
    }
}